/*
 This file 'template' is part of Firebird Integrated Solution 1.0

 Copyright (c) 2014 Lincong

 Contact:  
 Email: lincong1987@gmail.com

 QQ: 159257119

 See Usage at http://www.jplatformx.com/firebird

 Create date: 2014-06-24 15:48
 */

/*!
 * artTemplate - Template Engine
 * https://github.com/aui/artTemplate
 * Released under the MIT, BSD, and GPL Licenses
 */
define(function (require, exports, module) {

	module.exports = (function () {

		/**
		 * 模板引擎
		 * @name    template
		 * @param   {String}            模板名
		 * @param   {Object, String}    数据。如果为字符串则编译并缓存编译结果
		 * @return  {String, Function}  渲染好的HTML字符串或者渲染方法
		 */
		var template = function (filename, content) {
			return typeof content === 'string'
				? compile(content, {
				filename: filename
			})
				: renderFile(filename, content);
		};

		template.version = '3.0.0';

		/**
		 * 设置全局配置
		 * @name    template.config
		 * @param   {String}    名称
		 * @param   {Any}       值
		 */
		template.config = function (name, value) {
			defaults[name] = value;
		};

		var defaults = template.defaults = {
			debug: true,
			openTag: '{{',    // 逻辑语法开始标签
			closeTag: '}}',   // 逻辑语法结束标签
			escape: true,     // 是否编码输出变量的 HTML 字符
			cache: true,      // 是否开启缓存（依赖 options 的 filename 字段）
			compress: false,  // 是否压缩输出
			parser: null      // 自定义语法格式器 @see: template-syntax.js
		};

		var cacheStore = template.cache = {};

		/**
		 * 渲染模板
		 * @name    template.render
		 * @param   {String}    模板
		 * @param   {Object}    数据
		 * @return  {String}    渲染好的字符串
		 */
		template.render = function (source, options) {
			return compile(source, options);
		};

		/**
		 * 渲染模板(根据模板名)
		 * @name    template.render
		 * @param   {String}    模板名
		 * @param   {Object}    数据
		 * @return  {String}    渲染好的字符串
		 */
		var renderFile = template.renderFile = function (filename, data) {
			var fn = template.get(filename) || showDebugInfo({
					filename: filename,
					name: '模板渲染失败',
					message: '模板文件未找到'
				});
			return data ? fn(data) : fn;
		};

		/**
		 * 获取编译缓存（可由外部重写此方法）
		 * @param   {String}    模板名
		 * @param   {Function}  编译好的函数
		 */
		template.get = function (filename) {

			var cache;

			var test = require.resolve(filename);

			if (cacheStore[test + "#"]) {
				filename = test;
			}

			if (filename.indexOf(".tmpl") != -1 && cacheStore[filename + "#"]) {
				// 使用内存缓存
				cache = cacheStore[filename + "#"];
			} else if (typeof document === 'object') {

				// 加载模板并编译
				var elem = document.getElementById(filename);

				if (elem) {
					var source = (elem.value || elem.innerHTML)
						.replace(/^\s*|\s*$/g, '');
					cache = compile(source, {
						filename: filename
					});
				}
			}

			return cache;
		};

		var toString = function (value, type) {

			if (typeof value !== 'string') {

				type = typeof value;
				if (type === 'number') {
					value += '';
				} else if (type === 'function') {
					value = toString(value.call(value));
				} else {
					value = '';
				}
			}

			return value;

		};

		var escapeMap = {
			"<": "&#60;",
			">": "&#62;",
			'"': "&#34;",
			"'": "&#39;",
			"&": "&#38;"
		};

		var escapeFn = function (s) {
			return escapeMap[s];
		};

		var escapeHTML = function (content) {
			if (typeof content != "string") {
				return content;
			}
			return toString(content)
				.replace(/&(?![\w#]+;)|[<>"']/g, escapeFn);
		};

		var isArray = Array.isArray || function (obj) {
				return ({}).toString.call(obj) === '[object Array]';
			};

		var each = function (data, callback) {
			var i, len;
			if (isArray(data)) {
				for (i = 0, len = data.length; i < len; i++) {
					callback.call(data, data[i], i, data);
				}
			} else {
				for (i in data) {
					callback.call(data, data[i], i);
				}
			}
		};

		var utils = template.utils = {

			$helpers: {},

			$include: renderFile,

			$string: toString,

			$escape: escapeHTML,

			$each: each

		};
		/**
		 * 添加模板辅助方法
		 * @name    template.helper
		 * @param   {String}    名称
		 * @param   {Function}  方法
		 */
		template.helper = function (name, helper) {
			helpers[name] = helper;
		};

		var helpers = template.helpers = utils.$helpers;

		/**
		 * 模板错误事件（可由外部重写此方法）
		 * @name    template.onerror
		 * @event
		 */
		template.onerror = function (e) {
			var message = '前端模版处理时异常\n\n';
			for (var name in e) {
				message += '<' + name + '>\n' + e[name] + '\n\n';
			}

			if (typeof console === 'object') {
				console.error(message);
			}
		};

// 模板调试器
		var showDebugInfo = function (e) {

			template.onerror(e);

			return function () {
				return '{前端模版处理异常}';
			};
		};

		/**
		 * 编译模板
		 * 2012-6-6 @TooBug: define 方法名改为 compile，与 Node Express 保持一致
		 * @name    template.compile
		 * @param   {String}    模板字符串
		 * @param   {Object}    编译选项
		 *
		 *      - openTag       {String}
		 *      - closeTag      {String}
		 *      - filename      {String}
		 *      - escape        {Boolean}
		 *      - compress      {Boolean}
		 *      - debug         {Boolean}
		 *      - cache         {Boolean}
		 *      - parser        {Function}
		 *
		 * @return  {Function}  渲染方法
		 */
		var compile = template.compile = function (source, options) {

			// 合并默认配置
			options = options || {};
			for (var name in defaults) {
				if (options[name] === undefined) {
					options[name] = defaults[name];
				}
			}

			var filename = options.filename;

			try {

				var Render = compiler(source, options);

			} catch (e) {

				e.filename = filename || '匿名函数';
				e.name = '语法错误';

				return showDebugInfo(e);

			}

			// 对编译结果进行一次包装

			function render(data) {

				try {

					return new Render(data, filename) + '';

				} catch (e) {

					// 运行时出错后自动开启调试模式重新编译
					if (!options.debug) {
						options.debug = true;
						return compile(source, options)(data);
					}

					return showDebugInfo(e)();

				}

			}

			render.prototype = Render.prototype;
			render.toString = function () {
				return Render.toString();
			};

			if (filename && options.cache) {
				cacheStore[filename] = render;
			}

			return render;

		};

// 数组迭代
		var forEach = utils.$each;

// 静态分析模板变量
		var KEYWORDS =
			// 关键字
			'break,case,catch,continue,debugger,default,delete,do,else,false'
			+ ',finally,for,function,if,in,instanceof,new,null,return,switch,this'
			+ ',throw,true,try,typeof,var,void,while,with'

			// 保留字
			+ ',abstract,boolean,byte,char,class,const,double,enum,export,extends'
			+ ',final,float,goto,implements,import,int,interface,long,native'
			+ ',package,private,protected,public,short,static,super,synchronized'
			+ ',throws,transient,volatile'

			// ECMA 5 - use strict
			+ ',arguments,let,yield'

			+ ',undefined';

		var REMOVE_RE = /\/\*[\w\W]*?\*\/|\/\/[^\n]*\n|\/\/[^\n]*$|"(?:[^"\\]|\\[\w\W])*"|'(?:[^'\\]|\\[\w\W])*'|\s*\.\s*[$\w\.]+/g;
		var SPLIT_RE = /[^\w$]+/g;
		var KEYWORDS_RE = new RegExp(["\\b" + KEYWORDS.replace(/,/g, '\\b|\\b') + "\\b"].join('|'), 'g');
		var NUMBER_RE = /^\d[^,]*|,\d[^,]*/g;
		var BOUNDARY_RE = /^,+|,+$/g;

// 获取变量
		function getVariable(code) {
			return code
				.replace(REMOVE_RE, '')
				.replace(SPLIT_RE, ',')
				.replace(KEYWORDS_RE, '')
				.replace(NUMBER_RE, '')
				.replace(BOUNDARY_RE, '')
				.split(/^$|,+/);
		};

// 字符串转义
		function stringify(code) {
			return "'" + code
				// 单引号与反斜杠转义
					.replace(/('|\\)/g, '\\$1')
					// 换行符转义(windows + linux)
					.replace(/\r/g, '\\r')
					.replace(/\n/g, '\\n') + "'";
		}

		function compiler(source, options) {

			var debug = options.debug;
			var openTag = options.openTag;
			var closeTag = options.closeTag;
			var parser = options.parser;
			var compress = options.compress;
			var escape = options.escape;

			var line = 1;
			var uniq = {$data: 1, $filename: 1, $utils: 1, $helpers: 1, $out: 1, $line: 1};

			var isNewEngine = ''.trim;// '__proto__' in {}
			var replaces = isNewEngine
				? ["$out='';", "$out+=", ";", "$out"]
				: ["$out=[];", "$out.push(", ");", "$out.join('')"];

			var concat = isNewEngine
				? "$out+=text;return $out;"
				: "$out.push(text);";

			var print = "function(){"
				+ "var text=''.concat.apply('',arguments);"
				+ concat
				+ "}";

			var include = "function(filename,data){"
				+ "data=data||$data;"
				+ "var text=$utils.$include(filename,data,$filename);"
				+ concat
				+ "}";

			var headerCode = "'use strict';"
				+ "var $utils=this,$helpers=$utils.$helpers,"
				+ (debug ? "$line=0," : "");

			var mainCode = replaces[0];

			var footerCode = "return new String(" + replaces[3] + ");";

			// html与逻辑语法分离
			forEach(source.split(openTag), function (code) {
				code = code.split(closeTag);

				var $0 = code[0];
				var $1 = code[1];

				// code: [html]
				if (code.length === 1) {

					mainCode += html($0);

					// code: [logic, html]
				} else {

					mainCode += logic($0);

					if ($1) {
						mainCode += html($1);
					}
				}

			});

			var code = headerCode + mainCode + footerCode;

			// 调试语句
			if (debug) {
				code = "try{" + code + "}catch(e){"
					+ "throw {"
					+ "filename:$filename,"
					+ "name:'Render Error',"
					+ "message:e.message,"
					+ "line:$line,"
					+ "source:" + stringify(source)
					+ ".split(/\\n/)[$line-1].replace(/^\\s+/,'')"
					+ "};"
					+ "}";
			}

			try {

				var Render = new Function("$data", "$filename", code);
				Render.prototype = utils;

				return Render;

			} catch (e) {
				e.temp = "function anonymous($data,$filename) {" + code + "}";
				throw e;
			}

			// 处理 HTML 语句
			function html(code) {

				// 记录行号
				line += code.split(/\n/).length - 1;

				// 压缩多余空白与注释
				if (compress) {
					code = code
						.replace(/\s+/g, ' ')
						.replace(/<!--.*?-->/g, '');
				}

				if (code) {
					code = replaces[1] + stringify(code) + replaces[2] + "\n";
				}

				return code;
			}

			// 处理逻辑语句
			function logic(code) {

				var thisLine = line;

				if (parser) {

					// 语法转换插件钩子
					code = parser(code, options);

				} else if (debug) {

					// 记录行号
					code = code.replace(/\n/g, function () {
						line++;
						return "$line=" + line + ";";
					});

				}

				// 输出语句. 编码: <%=value%> 不编码:<%=#value%>
				// <%=#value%> 等同 v2.0.3 之前的 <%==value%>
				if (code.indexOf('=') === 0) {

					var escapeSyntax = escape && !/^=[=#]/.test(code);

					code = code.replace(/^=[=#]?|[\s;]*$/g, '');

					// 对内容编码
					if (escapeSyntax) {

						var name = code.replace(/\s*\([^\)]+\)/, '');

						// 排除 utils.* | include | print

						if (!utils[name] && !/^(include|print)$/.test(name)) {
							code = "$escape(" + code + ")";
						}

						// 不编码
					} else {
						code = "$string(" + code + ")";
					}

					code = replaces[1] + code + replaces[2];

				}

				if (debug) {
					code = "$line=" + thisLine + ";" + code;
				}

				// 提取模板中的变量名
				forEach(getVariable(code), function (name) {

					if (name == "Date") {
						return;
					}

					// name 值可能为空，在安卓低版本浏览器下
					if (!name || uniq[name]) {
						return;
					}

					var value;

					// 声明模板变量
					// 赋值优先级:
					// [include, print] > utils > helpers > data
					if (name === 'print') {

						value = print;

					} else if (name === 'include') {

						value = include;

					} else if (utils[name]) {

						value = "$utils." + name;

					} else if (helpers[name]) {

						value = "$helpers." + name;

					} else {

						value = "$data." + name;
					}

					headerCode += name + "=" + value + ",";
					uniq[name] = true;

				});

				return code + "\n";
			}

		};

// 定义模板引擎的语法

		defaults.openTag = '{{';
		defaults.closeTag = '}}';

		var filtered = function (js, filter) {
			var parts = filter.split(':');
			var name = parts.shift();
			var args = parts.join(':') || '';

			if (args) {
				args = ', ' + args;
			}

			return '$helpers.' + name + '(' + js + args + ')';
		};

		defaults.parser = function (code, options) {
			code = code.replace(/^\s/, '');

			var split = code.split(' ');
			var key = split.shift();
			var args = split.join(' ');

			switch (key) {

				case 'debug':

					code = ';debugger;';
					break;

				case 'if':

					code = 'if(' + args + '){';
					break;

				case 'else':

					if (split.shift() === 'if') {
						split = ' if(' + split.join(' ') + ')';
					} else {
						split = '';
					}

					code = '}else' + split + '{';
					break;

				case '/if':

					code = '}';
					break;

				case 'each':

					var object = split[0] || '$data';
					var as = split[1] || 'as';
					var value = split[2] || '$value';
					var index = split[3] || '$index';

					var param = value + ',' + index;

					if (as !== 'as') {
						object = '[]';
					}

					code = '$each(' + object + ',function(' + param + '){';
					break;

				case '/each':

					code = '});';
					break;

				case 'echo':

					code = 'print(' + args + ');';
					break;

				case 'print':
				case 'include':
					code = key + '(' + split.join(',') + ');';
					break;
				case 'set':
					//code = ";var " + split[0] + " = (function(a){return a;})(" + split[2] + ");";
					code = "; " + split[0] + " = (" + args.split("=")[1] + ");";
					break;
				//case '':
				//	//code = ";var " + split[0] + " = (function(a){return a;})(" + split[2] + ");";
				//	code = ";var " + split[0] + " = (" + args.split("=")[1] + ");";
				//	break;
				default:

					// 过滤器（辅助方法）
					// {{value | filterA:'abcd' | filterB}}
					// >>> $helpers.filterB($helpers.filterA(value, 'abcd'))
					if (args.indexOf('|') !== -1) {

						var escape = options.escape;

						// {{#value | link}}
						if (code.indexOf('#') === 0) {
							code = code.substr(1);
							escape = false;
						}

						var i = 0;
						var array = code.split('|');
						var len = array.length;
						var pre = escape ? '$escape' : '$string';
						var val = pre + '(' + array[i++] + ')';

						for (; i < len; i++) {
							val = filtered(val, array[i]);
						}

						code = '=#' + val;

						// 即将弃用 {{helperName value}}
					} else if (template.helpers[key]) {

						code = '=#' + key + '(' + split.join(',') + ');';

						// 内容直接输出 {{value}}
					} else {

						code = '=' + code;
					}

					break;
			}

			return code;
		};

		// 内置函数
		/**
		 * @param {*} value
		 * @param {*} def
		 * @param {Boolean} blank 空白检测 默认 true
		 */
		template.helper("defaults", function (value, def, blank) {
			blank = blank || true;
			if (typeof blank === "boolean" && blank === true) {
				return ( value == null || value == "" ) ? def : value;
			} else {
				return ( value == null) ? def : value;
			}
		});

		template.helper("replace", function (value, searchValue, replaceValue) {
			return value.replace(searchValue, replaceValue);
		});

		template.helper("date_format", function (value, format) {
			return firebird.date.format({time: value, format: format || "$yyyy-$MM-$dd"});
		});

		template.helper("dateFormat", function (value, format) {
			return firebird.date.format({time: value, format: format || "$yyyy-$MM-$dd"});
		});

		template.helper("byteLength", function (value) {
			return firebird.string.byteLength(value);
		});
		template.helper("byteSubstr", function (value, length, tail) {
			return firebird.string.byteSubstr(value, length, tail);
		});
		template.helper("decodeHTML", function (value) {
			return firebird.string.decodeHTML(value);
		});
		template.helper("encodeHTML", function (value) {
			return firebird.string.encodeHTML(value);
		});
		template.helper("toCamel", function (value) {
			return firebird.string.toCamel(value);
		});
		template.helper("trim", function (value) {
			return firebird.string.trim(value);
		});
		template.helper("cat", function (value, catValue) {
			return value + "" + catValue;
		});
		template.helper("lower", function (value) {
			return value.toLowerCase();
		});
		template.helper("upper", function (value) {
			return value.toUpperCase();
		});
		template.helper("nl2br", function (value) {
			return value.replace(/\n/ig, "<br>");
		});
		template.helper("n2p", function (value) {
			return "<p>" + value.replace(/\n/ig, "</p><p>") + "</p>";
		});

		template.helper("replaceContextPath", function (val) {
			return val.replace(/\{contextPath\}/g, (GLOBAL_CONFIG && GLOBAL_CONFIG.contextPath) || "");
		});

		template.helper("join", function (value, string) {
			return value.join(string);
		});

		template.helper("timeDiff", function (time) {
			if (!time) {
				return '';
			} else if (firebird.isDate(time)) {
				return repTime((new Date()).getTime() - time.getTime(), time);
			} else if (firebird.isNumber(time)) {
				//if (firebird.isString(time)) {
				time = new Date(time);
				//}
				return repTime((new Date()).getTime() - time.getTime(), time);
			} else {
				return repTime((new Date()).getTime() - (new Date(Date.parse(time.replace(/-/g, "/")))).getTime(), new Date(Date.parse(time.replace(/-/g, "/"))));
			}
			function repTime(differential, time) {
				if (differential <= 0) return '刚刚';
				differential = differential / 1000;
				//if(differential>86400)
				//return parseInt(differential/86400)+"天前";
				if (differential > 86400) {
					return time.getFullYear() + '-' + (time.getMonth() + 1) + '-' + time.getDate();
				}
				//return parseInt(differential/86400)+"天前";
				if (differential > 3600) {
					return parseInt(differential / 3600) + "小时前";
				}

				if (differential > 60) {
					return parseInt(differential / 60) + "分钟前";
				} else {
					return parseInt(differential) + "秒前";
				}
			}
		});

		return template;

	})();

});